// node基础库引入
const {resolve} = require('path');

//gulp相关引入
// 引入gulp
const gulp = require('gulp');
// 通过load-plugins加载gulp相关插件
const plugins = require('gulp-load-plugins')();

//其他引入
// const cmd = require('node-run-cmd');
const {exec} = require('./cmdPromise');
const del = require('del');
// 创建一个browser-sync的server对象
const server = require('browser-sync').create();
// 转化命令行参数为一个对象
const args = require('node-args-parser')(process.argv);
console.log('args', args);
// 处理相关命令行参数
// 提取prod参数供后续使用
const isProd = args['-production'] || args['-prod'] || false;
// const port = args['-port'] || 2080;
// const open = args['-open'] || false;
// 获取命令行执行目录
const CWD = process.cwd();

// 设置配置文件名称
const configFileName = 'project.config.js';
// 读取gulp 编译相关默认配置
let cfg = {
    data: require('./data'),
    config: require('./config')
};
try {
    const projectCfg = require(resolve(CWD, configFileName));
    cfg = Object.assign({}, cfg, projectCfg);
} catch (e) {
    console.log(`read ${configFileName} error`);
}
// 读取eslint相关配置
// 废弃，因为当手动传入参数至gulp-eslint插件时，插件的参数格式与.eslintrc.js不符合
// 例如，gulp-eslint插件的envs,globals属性均为数组形式，而.eslintrc.js对应的属性为对象
// 因此更好的方式应该是不处理参数，让插件在运行时自动读取运行时项目根目录的.eslintrc.js
// let eslintCfg = require('./.eslintrc');
// try {
//     const projectEslintCfg = require(resolve(CWD, '.eslintrc'));
//     eslintCfg = Object.assign({}, eslintCfg, projectEslintCfg);
//     console.log('eslintCfg', eslintCfg);
// } catch (e) {
//     console.log(`read .eslintrc error, using default eslint settings`);
// }

const {src, dest, series, parallel, watch} = gulp;

const {SRC, DIST, TEMP, PUBLIC, PATHS} = cfg.config;
// config快速生成函数
const generateConfig = (path) => ({base: path, cwd: path});

const srcConfig = generateConfig(SRC);
const distConfig = generateConfig(DIST);
const tempConfig = generateConfig(TEMP);
const publicConfig = generateConfig(PUBLIC);

// 清空dist/temp目录
const clean = () => {
    // del默认返回promise
    return del([DIST, TEMP]);
}

// style编译
const style = () => {
    return src(PATHS.style, srcConfig)
        .pipe(plugins.sass())
        .pipe(dest(TEMP))
        // 与watcher中的watch任务配合，实现浏览器刷新
        .pipe(server.reload({stream: true}))
}
// style lint
const styleLint = () => {
    return src(PATHS.style, srcConfig)
        .pipe(plugins.sassLint())
        .pipe(plugins.sassLint.format())
        // 如果报错终止执行
        .pipe(plugins.sassLint.failOnError())
}
// script编译
const script = () => {
    return src(PATHS.script, srcConfig)
        .pipe(plugins.babel({
            // 使用require引用，当变成cli时，require找到的是cli下的node_modules里的包
            presets: [require('@babel/preset-env')]
        }))
        .pipe(dest(TEMP))
        // 与watcher中的watch任务配合，实现浏览器刷新
        .pipe(server.reload({stream: true}))
}
// script lint task
const scriptLint = () => {
    return src(PATHS.script, srcConfig)
    // eslint() attaches the lint output to the "eslint" property
    // of the file object so it can be used by other modules.
    //执行lint
        .pipe(plugins.eslint())
        // eslint.format() outputs the lint results to the console.
        // Alternatively use eslint.formatEach() (see Docs).
        //输出lint结果至控制台
        .pipe(plugins.eslint.format())
        // To have the process exit with an error code (1) on
        // lint error, return the stream and pipe to failAfterError last.
        // 如果报错终止执行
        .pipe(plugins.eslint.failAfterError())
}
// page compile task
const page = () => {
    return src(PATHS.page, srcConfig)
        .pipe(plugins.swig({data: cfg.data, defaults: {cache: false}}))
        .pipe(dest(TEMP))
        // 与watcher中的watch任务配合，实现浏览器刷新
        .pipe(server.reload({stream: true}))
}

// image compile task
const image = () => {
    return src(PATHS.image, srcConfig)
    // 仅在prod模式下进行压缩
        .pipe(plugins.if(isProd, plugins.imagemin()))
        .pipe(dest(DIST));
}

// font zip task
const font = () => {
    return src(PATHS.font, srcConfig)
    // 仅在prod模式下进行压缩
        .pipe(plugins.if(isProd, plugins.imagemin()))
        .pipe(dest(DIST));
}

// public extra task
const extra = () => {
    return src('**', publicConfig)
        .pipe(dest(DIST));
}
const useref = () => {
    return src(PATHS.page, tempConfig)
    // tempConfig中的cwd：TEMP会将useref的工作目录改为TEMP，
    // 加上..才是项目根目录，从而找到node_modules
        .pipe(plugins.useref({searchPath: [TEMP, '.', '..']}))
        // 仅在prod模式下进行相应文件压缩
        .pipe(plugins.if(isProd && /\.css$/, plugins.cleanCss()))
        .pipe(plugins.if(isProd && /\.js$/, plugins.uglify()))
        .pipe(plugins.if(isProd && /\.html$/, plugins.htmlmin({
            collapseWhitespace: true,
            removeComments: true,
            minifyJS: true,
            minifyCSS: true,
        })))
        .pipe(dest(DIST));
}
const watcher = () => {
    // 开发模式下监听文件变化；prod模式不监听
    if (!isProd) {
        // 监听相关css/js/html文件，并重新执行对应的编译
        watch(PATHS.style, srcConfig, style);
        watch(PATHS.script, srcConfig, script);
        watch(PATHS.page, srcConfig, page);
        // 监听图片/字体/其他静态资源文件，刷新浏览器
        watch([PATHS.image, PATHS.font], srcConfig, server.reload);
        watch('**', publicConfig, server.reload);
    }
    const serverCfg = {
        // prod模式只使用dist目录
        // 开发模式下，需使用temp里的css/js/html文件，src下的图片/字体文件，public下的其他静态资源文件
        baseDir: isProd ? [DIST] : [TEMP, SRC, PUBLIC],
        // prod模式下，不需要任何路由；开发模式下，需要通过路由找到/node_modules下的vendor文件
        routes: !isProd && {
            '/node_modules': 'node_modules',
        }
    }
    server.init({
        // 优先使用命令行参数的设置
        port: args['-port'] || 2080,
        open: args['-open'] || false,
        server: {
            ...serverCfg,
        },
    })
}
const onData = (data) => {
    console.log(data)
};
// 使用node-cmd插件异步执行git cmd
const git = (done) => {
    return exec(`git add ./${DIST}`)
        .then((data) => {
            console.log('git add success', data);
            return exec(`git commit -m "commit ${DIST} ${new Date().toLocaleString()}"`)
        })
        .then((data) => {
            console.log('git commit success', data);
            return exec(`git push origin ${args['-branch'] || 'gh-pages'}`)
        })
        .then((data) => {
            console.log('git push success', data);
        })
        .catch(e => {
            console.log(e);
            done(false);
        })
}
// lint命令，并行执行，一是缩短时间，二是因为任何一项lint任务一旦报错都无需往下继续
const lint = parallel(styleLint, scriptLint);
// 编译，并行执行
const compile = parallel(style, script, page);
// const prodCompile = parallel(series(compile, useref), image, font, extra);
// 打包命令，需要先clean，然后并行执行（css/js/html的编译与引用查找，以及其他静态资源的压缩）
const build = series(clean, parallel(series(compile, useref), image, font, extra));
// 开发模式下的serve，编译加watcher即可
const serveDev = series(compile, watcher);
// prod模式下的serve，需要先打包，然后watcher即可。
// 这时候watcher中不监听任何文件变化，相关逻辑在watcher中处理
const serveProd = series(build, watcher);
// 对外暴露的serve命令，根据命令行prod参数执行不同的serve
const serve = isProd ? serveProd : serveDev;
// deploy命令，先打包，再git
const deploy = series(build, git);
module.exports = {
    lint,
    compile,
    serve,
    build,
    clean,
    deploy,
    //lint sub tasks
    // styleLint,
    // scriptLint,

    // build sub tasks
    // style,
    // script,
    // page,
    // image,
    // font,
    // extra,
    // useref,
}